import { VMUtil } from "./VMUtil";

/**VM 数组操作，若数组单个值数据变化，则可以通过update 函数刷新数据 */
export class VMArray {
  private static _instance: VMArray;
  public static get instance(): VMArray {
    if (this._instance == null) {
      this._instance = new VMArray();
    }
    return this._instance;
  }

  // 数组处理
  arrayProto = Array.prototype;

  arrayMethods = Object.create(this.arrayProto);
  arrayKeys = Object.getOwnPropertyNames(this.arrayMethods);
  // 判断当前环境是否可以使用 __proto__
  hasProto = "__proto__" in {};

  methodList = [
    "push",
    "pop",
    "shift",
    "unshift",
    "splice",
    "sort",
    "reverse",
    "update",
  ];

  constructor() {
    this.methodList.forEach((method) => {
      // 原始数组操作方法
      let original = this.arrayMethods[method];
      VMUtil.instance.Define(this.arrayMethods, method, function () {
        let arguments$1 = arguments;
        let i = arguments.length;
        let args = new Array(i);
        //  订阅者关联属性
        let ob = this.__ob__;
        while (i--) {
          args[i] = arguments$1[i];
        }
        if (method == "update") {
          ob.depSub.notify();
          return;
        }
        // 执行数组方法
        let result = original.apply(this, args);

        // 存放新增数组元素
        let inserted;
        // 为add 进arry中的元素进行observe
        switch (method) {
          case "push":
            inserted = args;
            break;
          case "unshift":
            inserted = args;
            break;
          case "splice":
            // 第三个参数开始才是新增元素
            inserted = args.slice(2);
            break;
        }
        if (inserted) {
          ob.observeArray(inserted);
        }
        // 通知数组变化
        ob.depSub.notify();
        // 返回新数组长度
        return result;
      });
    });
  }
  // 直接将对象的 proto 指向 src这一组方法
  protoAugment(target, src) {
    target.__proto__ = src;
  }
  // 遍历这一组方法，依次添加到对象中，作为隐藏属性（即 enumerable: false，不能被枚举）
  copyAugment(target, src, keys) {
    for (let i = 0, l = keys.length; i < l; i++) {
      let key = keys[i];
      VMUtil.instance.Define(target, key, src[key]);
    }
  }

  setAugment(value) {
    let augment = this.hasProto ? this.protoAugment : this.copyAugment;
    augment(value, this.arrayMethods, this.arrayKeys);
  }
}
